﻿//------------------------------------------------------------------------------
// <copyright file="RpcExceptionFactory.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------

namespace Microsoft.Internal.GamesTest.Rpc.Client
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Runtime.InteropServices;

    /// <summary>
    /// Represents a translator of error codes returned by Rpc hooks into exceptions.
    /// </summary>
    internal static class RpcExceptionFactory
    {
        private static Dictionary<int, Func<RpcRequest, RpcException>> rpcErrorTypeMap = new Dictionary<int, Func<RpcRequest, RpcException>>()
        {
            { RpcErrorCodes.GamesTestRpcErrorUnknownServerId, request => new RpcServerIdNotFoundException("Could not locate remote server with given Id.") },
            { RpcErrorCodes.GamesTestRpcErrorFailedToParseRpcSocketRequest, request => new RpcSocketTransportException("There was an error with the underlying sockets protocol.") },
            { RpcErrorCodes.HresultInvalidJson, request => new RpcInvalidJsonException("Improperly formatted JSON request received by server.") }, 
            { RpcErrorCodes.HresultInvalidDataFormat, request => new RpcProtocolFormatViolationException("Invalid data format for the specific protocol operation.") },
            { RpcErrorCodes.HresultRpcArchiveMemberNotFound, request => new RpcArchiveFieldNotFoundException("Hook failed because the requested field was not found in the RpcArchive.") },
            { RpcErrorCodes.HresultRpcArchiveValueIsNull, request => new RpcArchiveValueIsNullException("An unexpected null value was found in the RpcArchive.") },
            { RpcErrorCodes.HresultStringBufferTooSmall, request => new RpcStringBufferTooSmallException("The buffer provided to retrieve a string from an RpcArchive was too small.") },
            { RpcErrorCodes.HresultRequestQueueFull, request => new RpcRequestQueueFullException("The request was rejected because the server request queue is full.") },
            { RpcErrorCodes.HresultRpcVersionMismatch, request => new RpcVersionException("The version of the client does not match the version of the server.") },
            { RpcErrorCodes.GamesTestRpcErrorMethodNotFound, request =>
                {
                    if (request != null)
                    {
                        return new RpcRemoteMethodNotFoundException(string.Format(CultureInfo.InvariantCulture, "Could not locate remote method '{0}'.", request.MethodName), request.MethodName); 
                    }
                    else
                    {
                        // This exception is targeted at developers of the TDK RPC more so than toward its users.
                        return new RpcRemoteMethodNotFoundException("Unable to invoke remote method.  If available, provide the originating RpcRequest object to the RpcExceptionFactory for more detailed information."); 
                    }
                }
            },
            { RpcErrorCodes.GamesTestRpcErrorFailedToSerializeUserData, request => 
                {
                    if (request != null)
                    {
                        return new RpcResponseArchiveSerializationException(string.Format(CultureInfo.InvariantCulture, "The result from the remote method '{0}' could not be serialized.", request.MethodName));
                    }
                    else
                    {
                        return new RpcResponseArchiveSerializationException("The result from a remote method could not be serialized.  If available, provide the originating RpcRequest object to the RpcExceptionFactory for more detailed information.");
                    }
                }
            }
        };

        /// <summary>
        /// Throws an exception for a given Rpc response error code.
        /// </summary>
        /// <param name="responseResultCode">The error code to be considered.</param>
        public static void ThrowExceptionForRpcResponseCode(int responseResultCode)
        {
            ThrowExceptionForRpcResponseCode(null, responseResultCode);
        }

        /// <summary>
        /// Throws an exception for the given response error codes:
        /// RpcErrorCode and HookErrorCode.
        /// </summary>
        /// <param name="request">The original request sent to the server.</param>
        /// <param name="response">The response sent back by the server.</param>
        /// <param name="exceptionFactory">Factory that will be used to map the remote method return code to an exception.</param>
        public static void ThrowExceptionForResponseCodes(RpcRequest request, RpcResponse response, IExceptionFactory exceptionFactory)
        {
            // Handle errors generated by TDK RPC first
            ThrowExceptionForRpcResponseCode(request, response.RpcErrorCode);

            // Handle user-defined error codes next
            RpcHookCode hookErrorCode = RpcHookCode.CreateFromInt(response.HookErrorCode);

            if (hookErrorCode == RpcHookCode.Success)
            {
                return;
            }

            if (exceptionFactory != null)
            {
                Exception customException = exceptionFactory.CreateExceptionForErrorCode(request.MethodName, hookErrorCode, request.Parameters, response.Archive);
                if (customException != null)
                {
                    throw customException;
                }
            }

            string unknownErrorMsg = "An unknown hook error code was received: {0}.\n" +
                                     "To receive more meaninful information regarding this code, make sure you register an IExceptionFactory using RpcChannel.RegisterExceptionFactory().\n" +
                                     "Additionally, make sure you provide more data by mapping your code to an exception using IExceptionFactory.CreateExceptionForErrorCode().\n" +
                                     "Microsoft.Internal.GamesTest.Rpc.Client.RpcHookCode and RpcHookCode.h (Server Include) can provide you with additional guidance.";

            throw new UnknownHookErrorCodeException(string.Format(CultureInfo.InvariantCulture, unknownErrorMsg, hookErrorCode));
        }

        /// <summary>
        /// Throws an exception for a given Rpc response error code.
        /// </summary>
        /// <param name="requestMessage">The original request sent to the server.</param>
        /// <param name="responseResultCode">The error code contained in the response.</param>
        private static void ThrowExceptionForRpcResponseCode(RpcRequest requestMessage, int responseResultCode)
        {
            if (responseResultCode == RpcErrorCodes.Success)
            {
                return;
            }

            Func<RpcRequest, RpcException> exceptionCreator;
            if (rpcErrorTypeMap.TryGetValue(responseResultCode, out exceptionCreator))
            {
                throw exceptionCreator(requestMessage);
            }

            Exception comException = Marshal.GetExceptionForHR(responseResultCode);
            if (comException != null)
            {
                throw new RpcException("Unexpected error occurred.  See inner exception for details.", comException);
            }

            throw new RpcException(string.Format(CultureInfo.InvariantCulture, "Unknown error occurred. The error code did not map to a known exception type. Error code: {0}", responseResultCode));
        }
    }
}
